#region Copyright & License

//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#endregion

using System;
using System.Collections;
using System.Globalization;
using System.IO;
using log4net.Core;
using log4net.Util.PatternStringConverters;

namespace log4net.Util {
  /// <summary>
  /// This class implements a patterned string.
  /// </summary>
  /// <remarks>
  /// <para>
  /// This string has embedded patterns that are resolved and expanded
  /// when the string is formatted.
  /// </para>
  /// <para>
  /// This class functions similarly to the <see cref="log4net.Layout.PatternLayout"/>
  /// in that it accepts a pattern and renders it to a string. Unlike the 
  /// <see cref="log4net.Layout.PatternLayout"/> however the <c>PatternString</c>
  /// does not render the properties of a specific <see cref="LoggingEvent"/> but
  /// of the process in general.
  /// </para>
  /// <para>
  /// The recognized conversion pattern names are:
  /// </para>
  /// <list type="table">
  ///     <listheader>
  ///         <term>Conversion Pattern Name</term>
  ///         <description>Effect</description>
  ///     </listheader>
  ///     <item>
  ///         <term>appdomain</term>
  ///         <description>
  ///             <para>
  ///             Used to output the friendly name of the current AppDomain.
  ///             </para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>date</term>
  ///         <description>
  /// 			<para>
  /// 			Used to output the date of the logging event in the local time zone. 
  /// 			To output the date in universal time use the <c>%utcdate</c> pattern.
  /// 			The date conversion 
  /// 			specifier may be followed by a <i>date format specifier</i> enclosed 
  /// 			between braces. For example, <b>%date{HH:mm:ss,fff}</b> or
  /// 			<b>%date{dd MMM yyyy HH:mm:ss,fff}</b>.  If no date format specifier is 
  /// 			given then ISO8601 format is
  /// 			assumed (<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>).
  /// 			</para>
  /// 			<para>
  /// 			The date format specifier admits the same syntax as the
  /// 			time pattern string of the <see cref="DateTime.ToString(string)"/>.
  /// 			</para>
  /// 			<para>
  /// 			For better results it is recommended to use the log4net date
  /// 			formatters. These can be specified using one of the strings
  /// 			"ABSOLUTE", "DATE" and "ISO8601" for specifying 
  /// 			<see cref="log4net.DateFormatter.AbsoluteTimeDateFormatter"/>, 
  /// 			<see cref="log4net.DateFormatter.DateTimeDateFormatter"/> and respectively 
  /// 			<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>. For example, 
  /// 			<b>%date{ISO8601}</b> or <b>%date{ABSOLUTE}</b>.
  /// 			</para>
  /// 			<para>
  /// 			These dedicated date formatters perform significantly
  /// 			better than <see cref="DateTime.ToString(string)"/>.
  /// 			</para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>env</term>
  ///         <description>
  ///             <para>
  /// 			Used to output the a specific environment variable. The key to 
  /// 			lookup must be specified within braces and directly following the
  /// 			pattern specifier, e.g. <b>%env{COMPUTERNAME}</b> would include the value
  /// 			of the <c>COMPUTERNAME</c> environment variable.
  ///             </para>
  ///             <para>
  ///             The <c>env</c> pattern is not supported on the .NET Compact Framework.
  ///             </para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>identity</term>
  ///         <description>
  ///				<para>
  ///				Used to output the user name for the currently active user
  ///				(Principal.Identity.Name).
  ///				</para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>newline</term>
  ///         <description>
  /// 			<para>
  /// 			Outputs the platform dependent line separator character or
  /// 			characters.
  /// 			</para>
  /// 			<para>
  /// 			This conversion pattern name offers the same performance as using 
  /// 			non-portable line separator strings such as	"\n", or "\r\n". 
  /// 			Thus, it is the preferred way of specifying a line separator.
  /// 			</para> 
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>processid</term>
  ///         <description>
  ///             <para>
  ///				Used to output the system process ID for the current process.
  ///             </para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>property</term>
  ///         <description>
  /// 			<para>
  /// 			Used to output a specific context property. The key to 
  /// 			lookup must be specified within braces and directly following the
  /// 			pattern specifier, e.g. <b>%property{user}</b> would include the value
  /// 			from the property that is keyed by the string 'user'. Each property value
  /// 			that is to be included in the log must be specified separately.
  /// 			Properties are stored in logging contexts. By default 
  /// 			the <c>log4net:HostName</c> property is set to the name of machine on 
  /// 			which the event was originally logged.
  /// 			</para>
  /// 			<para>
  /// 			If no key is specified, e.g. <b>%property</b> then all the keys and their
  /// 			values are printed in a comma separated list.
  /// 			</para>
  /// 			<para>
  /// 			The properties of an event are combined from a number of different
  /// 			contexts. These are listed below in the order in which they are searched.
  /// 			</para>
  /// 			<list type="definition">
  /// 				<item>
  /// 					<term>the thread properties</term>
  /// 					<description>
  /// 					The <see cref="ThreadContext.Properties"/> that are set on the current
  /// 					thread. These properties are shared by all events logged on this thread.
  /// 					</description>
  /// 				</item>
  /// 				<item>
  /// 					<term>the global properties</term>
  /// 					<description>
  /// 					The <see cref="GlobalContext.Properties"/> that are set globally. These 
  /// 					properties are shared by all the threads in the AppDomain.
  /// 					</description>
  /// 				</item>
  /// 			</list>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>random</term>
  ///         <description>
  ///             <para>
  ///             Used to output a random string of characters. The string is made up of
  ///             uppercase letters and numbers. By default the string is 4 characters long.
  ///             The length of the string can be specified within braces directly following the
  /// 			pattern specifier, e.g. <b>%random{8}</b> would output an 8 character string.
  ///             </para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>username</term>
  ///         <description>
  ///				<para>
  ///				Used to output the WindowsIdentity for the currently
  ///				active user.
  ///				</para>
  ///         </description>
  ///     </item>
  ///     <item>
  ///         <term>utcdate</term>
  ///         <description>
  /// 			<para>
  /// 			Used to output the date of the logging event in universal time. 
  /// 			The date conversion 
  /// 			specifier may be followed by a <i>date format specifier</i> enclosed 
  /// 			between braces. For example, <b>%utcdate{HH:mm:ss,fff}</b> or
  /// 			<b>%utcdate{dd MMM yyyy HH:mm:ss,fff}</b>.  If no date format specifier is 
  /// 			given then ISO8601 format is
  /// 			assumed (<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>).
  /// 			</para>
  /// 			<para>
  /// 			The date format specifier admits the same syntax as the
  /// 			time pattern string of the <see cref="DateTime.ToString(string)"/>.
  /// 			</para>
  /// 			<para>
  /// 			For better results it is recommended to use the log4net date
  /// 			formatters. These can be specified using one of the strings
  /// 			"ABSOLUTE", "DATE" and "ISO8601" for specifying 
  /// 			<see cref="log4net.DateFormatter.AbsoluteTimeDateFormatter"/>, 
  /// 			<see cref="log4net.DateFormatter.DateTimeDateFormatter"/> and respectively 
  /// 			<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>. For example, 
  /// 			<b>%utcdate{ISO8601}</b> or <b>%utcdate{ABSOLUTE}</b>.
  /// 			</para>
  /// 			<para>
  /// 			These dedicated date formatters perform significantly
  /// 			better than <see cref="DateTime.ToString(string)"/>.
  /// 			</para>
  ///         </description>
  ///     </item>
  ///		<item>
  ///			<term>%</term>
  ///			<description>
  /// 			<para>
  /// 			The sequence %% outputs a single percent sign.
  /// 			</para>
  ///			</description>
  ///		</item>
  /// </list>
  /// <para>
  /// Additional pattern converters may be registered with a specific <see cref="PatternString"/>
  /// instance using <see cref="AddConverter(PatternString.ConverterInfo)"/> or
  /// <see cref="AddConverter(string, Type)" />.
  /// </para>
  /// <para>
  /// See the <see cref="log4net.Layout.PatternLayout"/> for details on the 
  /// <i>format modifiers</i> supported by the patterns.
  /// </para>
  /// </remarks>
  /// <author>Nicko Cadell</author>
  public class PatternString : IOptionHandler {
    #region Static Fields

    /// <summary>
    /// Internal map of converter identifiers to converter types.
    /// </summary>
    static readonly Hashtable s_globalRulesRegistry;

    #endregion Static Fields

    #region Member Variables

    /// <summary>
    /// patterns defined on this PatternString only
    /// </summary>
    readonly Hashtable m_instanceRulesRegistry = new Hashtable();

    /// <summary>
    /// the head of the pattern converter chain
    /// </summary>
    PatternConverter m_head;

    /// <summary>
    /// the pattern
    /// </summary>
    string m_pattern;

    #endregion

    #region Static Constructor

    /// <summary>
    /// Initialize the global registry
    /// </summary>
    static PatternString() {
      s_globalRulesRegistry = new Hashtable(15);

      s_globalRulesRegistry.Add("appdomain", typeof (AppDomainPatternConverter));
      s_globalRulesRegistry.Add("date", typeof (DatePatternConverter));
#if !NETCF
      s_globalRulesRegistry.Add("env", typeof (EnvironmentPatternConverter));
#endif
      s_globalRulesRegistry.Add("identity", typeof (IdentityPatternConverter));
      s_globalRulesRegistry.Add("literal", typeof (LiteralPatternConverter));
      s_globalRulesRegistry.Add("newline", typeof (NewLinePatternConverter));
      s_globalRulesRegistry.Add("processid", typeof (ProcessIdPatternConverter));
      s_globalRulesRegistry.Add("property", typeof (PropertyPatternConverter));
      s_globalRulesRegistry.Add("random", typeof (RandomStringPatternConverter));
      s_globalRulesRegistry.Add("username", typeof (UserNamePatternConverter));

      s_globalRulesRegistry.Add("utcdate", typeof (UtcDatePatternConverter));
      s_globalRulesRegistry.Add("utcDate", typeof (UtcDatePatternConverter));
      s_globalRulesRegistry.Add("UtcDate", typeof (UtcDatePatternConverter));
    }

    #endregion Static Constructor

    #region Constructors

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <remarks>
    /// <para>
    /// Initialize a new instance of <see cref="PatternString"/>
    /// </para>
    /// </remarks>
    public PatternString() {}

    /// <summary>
    /// Constructs a PatternString
    /// </summary>
    /// <param name="pattern">The pattern to use with this PatternString</param>
    /// <remarks>
    /// <para>
    /// Initialize a new instance of <see cref="PatternString"/> with the pattern specified.
    /// </para>
    /// </remarks>
    public PatternString(string pattern) {
      m_pattern = pattern;
      ActivateOptions();
    }

    #endregion

    /// <summary>
    /// Gets or sets the pattern formatting string
    /// </summary>
    /// <value>
    /// The pattern formatting string
    /// </value>
    /// <remarks>
    /// <para>
    /// The <b>ConversionPattern</b> option. This is the string which
    /// controls formatting and consists of a mix of literal content and
    /// conversion specifiers.
    /// </para>
    /// </remarks>
    public string ConversionPattern {
      get { return m_pattern; }
      set { m_pattern = value; }
    }

    #region Implementation of IOptionHandler

    /// <summary>
    /// Initialize object options
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is part of the <see cref="IOptionHandler"/> delayed object
    /// activation scheme. The <see cref="ActivateOptions"/> method must 
    /// be called on this object after the configuration properties have
    /// been set. Until <see cref="ActivateOptions"/> is called this
    /// object is in an undefined state and must not be used. 
    /// </para>
    /// <para>
    /// If any of the configuration properties are modified then 
    /// <see cref="ActivateOptions"/> must be called again.
    /// </para>
    /// </remarks>
    public virtual void ActivateOptions() {
      m_head = CreatePatternParser(m_pattern).Parse();
    }

    #endregion

    /// <summary>
    /// Create the <see cref="PatternParser"/> used to parse the pattern
    /// </summary>
    /// <param name="pattern">the pattern to parse</param>
    /// <returns>The <see cref="PatternParser"/></returns>
    /// <remarks>
    /// <para>
    /// Returns PatternParser used to parse the conversion string. Subclasses
    /// may override this to return a subclass of PatternParser which recognize
    /// custom conversion pattern name.
    /// </para>
    /// </remarks>
    PatternParser CreatePatternParser(string pattern) {
      var patternParser = new PatternParser(pattern);

      // Add all the builtin patterns
      foreach (DictionaryEntry entry in s_globalRulesRegistry)
        patternParser.PatternConverters.Add(entry.Key, entry.Value);
      // Add the instance patterns
      foreach (DictionaryEntry entry in m_instanceRulesRegistry)
        patternParser.PatternConverters[entry.Key] = entry.Value;

      return patternParser;
    }

    /// <summary>
    /// Produces a formatted string as specified by the conversion pattern.
    /// </summary>
    /// <param name="writer">The TextWriter to write the formatted event to</param>
    /// <remarks>
    /// <para>
    /// Format the pattern to the <paramref name="writer"/>.
    /// </para>
    /// </remarks>
    public void Format(TextWriter writer) {
      if (writer == null)
        throw new ArgumentNullException("writer");

      PatternConverter c = m_head;

      // loop through the chain of pattern converters
      while (c != null) {
        c.Format(writer, null);
        c = c.Next;
      }
    }

    /// <summary>
    /// Format the pattern as a string
    /// </summary>
    /// <returns>the pattern formatted as a string</returns>
    /// <remarks>
    /// <para>
    /// Format the pattern to a string.
    /// </para>
    /// </remarks>
    public string Format() {
      var writer = new StringWriter(CultureInfo.InvariantCulture);
      Format(writer);
      return writer.ToString();
    }

    /// <summary>
    /// Add a converter to this PatternString
    /// </summary>
    /// <param name="converterInfo">the converter info</param>
    /// <remarks>
    /// <para>
    /// This version of the method is used by the configurator.
    /// Programmatic users should use the alternative <see cref="AddConverter(string,Type)"/> method.
    /// </para>
    /// </remarks>
    public void AddConverter(ConverterInfo converterInfo) {
      AddConverter(converterInfo.Name, converterInfo.Type);
    }

    /// <summary>
    /// Add a converter to this PatternString
    /// </summary>
    /// <param name="name">the name of the conversion pattern for this converter</param>
    /// <param name="type">the type of the converter</param>
    /// <remarks>
    /// <para>
    /// Add a converter to this PatternString
    /// </para>
    /// </remarks>
    public void AddConverter(string name, Type type) {
      if (name == null)
        throw new ArgumentNullException("name");
      if (type == null)
        throw new ArgumentNullException("type");

      if (!typeof (PatternConverter).IsAssignableFrom(type))
        throw new ArgumentException(
          "The converter type specified [" + type + "] must be a subclass of log4net.Util.PatternConverter",
          "type");
      m_instanceRulesRegistry[name] = type;
    }

    #region Nested type: ConverterInfo

    /// <summary>
    /// Wrapper class used to map converter names to converter types
    /// </summary>
    /// <remarks>
    /// <para>
    /// Wrapper class used to map converter names to converter types
    /// </para>
    /// </remarks>
    public sealed class ConverterInfo {
      /// <summary>
      /// Gets or sets the name of the conversion pattern
      /// </summary>
      /// <value>
      /// The name of the conversion pattern
      /// </value>
      /// <remarks>
      /// <para>
      /// Gets or sets the name of the conversion pattern
      /// </para>
      /// </remarks>
      public string Name { get; set; }

      /// <summary>
      /// Gets or sets the type of the converter
      /// </summary>
      /// <value>
      /// The type of the converter
      /// </value>
      /// <remarks>
      /// <para>
      /// Gets or sets the type of the converter
      /// </para>
      /// </remarks>
      public Type Type { get; set; }
    }

    #endregion
  }
}